#pragma once

#include <string>
#include <thread>
#include <vector>
#include <functional>

#include "AABB.h"
#include "SparseGrid.h"
#include "CloudJS.hpp"
#include "PointAttributes.hpp"
#include "potreelib_global.h"

using std::string;
using std::thread;
using std::vector;

namespace Potree {

	class PotreeReader;
	class PointReader;

	class PRNode {

	public:
		int index = -1;
		AABB aabb;
		AABB acceptedAABB;
		int level = 0;
		SparseGrid* grid;
		unsigned int numAccepted = 0;
		PRNode* parent = NULL;
		vector<PRNode*> children;
		bool addedSinceLastFlush = true;
		bool addCalledSinceLastFlush = false;
		PotreeReader* potreeReader;
		vector<Point> cache;
		//int storeLimit = 20'000;
		vector<Point> store;
		bool isInMemory = true;

		PRNode(PotreeReader* potreeReader, AABB aabb);

		PRNode(PotreeReader* potreeReader, int index, AABB aabb, int level);

		~PRNode();

		string name() const;

		float spacing();

		bool isLeafNode() const {
			return children.size() == 0;
		}

		bool isInnerNode() {
			return children.size() > 0;
		}

		void loadFromDisk();

		PRNode* add(Point& point);

		PRNode* createChild(int childIndex);

		void split();

		string workDir();

		string hierarchyPath();

		string path();

		void flush();

		void traverse(std::function<void(PRNode*)> callback);

		void traverseBreadthFirst(std::function<void(PRNode*)> callback);

		vector<PRNode*> getHierarchy(int levels);

		PRNode* findNode(string name);
		static void loadNode(PRNode* node);
		static long long checkPointCount(PRNode* node);

		uint64_t getPointCount()
		{
			if (isLeafNode())
			{
				return store.size();
			}
			else return numAccepted;
		}

	private:

		PointReader* createReader(string path);

	};

	class POTREELIB_EXPORT PotreeReader
	{
	public:

		AABB aabb;
		AABB tightAABB;
		string workDir;
		float spacing;
		double scale = 0;
		int maxDepth = -1;
		PRNode* root;
		long long numAdded = 0;
		long long numAccepted = 0;
		CloudJS cloudjs;
		OutputFormat outputFormat;
		PointAttributes pointAttributes;
		int hierarchyStepSize = 5;
		vector<Point> store;
		thread storeThread;
		int pointsInMemory = 0;
		string projection = "";
		ConversionQuality quality = ConversionQuality::DEFAULT;
		int storeSize = 20'000;

		PotreeReader(string workDir, ConversionQuality quality);

		PotreeReader(string workDir, AABB aabb, float spacing, int maxDepth, double scale, OutputFormat outputFormat, PointAttributes pointAttributes, ConversionQuality quality);

		~PotreeReader() {
			close();

			delete root;
		}

		string getExtension();

		//void flush();

		void close() {
			//flush();
		}

		void loadStateFromDisk();
		void load();
		long long checkPointCount();
	};

}
